#include "common.h"
#include "counter.h"
#include "Vfreelist.h"

#define TRACE 1
#define MAX_WAVE 1000000
#define MAX_CYCLE 1000000
#define RENAME_WIDTH 2
#define COMMIT_WIDTH 4
Vfreelist top;
VerilatedFstC *tfp;
static uint64_t wave_cnt = 0;
void dump()
{
#if TRACE
    if (wave_cnt < MAX_WAVE)
    {
        tfp->dump(Verilated::time());
        wave_cnt++;
    }
#endif
}
void run()
{
    top.clk = 0;
    top.eval();
    dump();
    Verilated::timeInc(1);
    top.clk = 1;
    top.eval();
    dump();
    Verilated::timeInc(1);
}
void reset()
{
    for (size_t i = 0; i < RENAME_WIDTH; i++)
    {
        top.o_allocate_ready[i] = 0;
    }

    for (size_t i = 0; i < COMMIT_WIDTH; i++)
    {
        top.i_free_valid[i] = 0;
    }
    top.rst = 1;
    run();
    run();
    run();
    top.rst = 0;
}

Counter cycle("cycle");

void test_reset()
{
    reset();
    size_t free_cnt = 0;
    bool sim_finish = false;
    do
    {
        run();
        cycle++;
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            top.o_allocate_ready[i] = 1;
        }
        sim_finish = true;
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            if (top.o_allocate_valid[i] && top.o_allocate_ready[i])
            {
                free_cnt++;
                sim_finish = false;
                LOG("Allocate %ld Reg %d, rptr 0x%x at %ld", free_cnt, top.o_allocate_entry[i], top.o_allocate_rptr[i], wave_cnt);
            }
        }
    } while (!sim_finish && cycle < MAX_CYCLE);
}

void test_free()
{
    reset();
    size_t free_cnt = 0;
    bool sim_finish = false;
    do
    {
        run();
        cycle++;
        for (size_t i = 0; i < COMMIT_WIDTH; i++)
        {
            top.i_free_valid[i] = 1;
        }
        sim_finish = true;
        for (size_t i = 0; i < COMMIT_WIDTH; i++)
        {
            if (top.i_free_ready && top.i_free_valid[i])
            {
                LOG("Free %ld at %ld", free_cnt, wave_cnt);
                top.i_free_entry[i] = free_cnt++;
                sim_finish = false;
            }
        }
    } while (!sim_finish && cycle < MAX_CYCLE);
    for (size_t i = 0; i < COMMIT_WIDTH; i++)
    {
        top.i_free_valid[i] = 0;
    }

    size_t allocate_cnt = 0;
    sim_finish = false;
    do
    {
        run();
        cycle++;
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            top.o_allocate_ready[i] = 1;
        }
        sim_finish = true;
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            if (top.o_allocate_valid[i] && top.o_allocate_ready[i])
            {
                allocate_cnt++;
                sim_finish = false;
                LOG("Allocate %ld Reg %d, rptr 0x%x at %ld", allocate_cnt, top.o_allocate_entry[i], top.o_allocate_rptr[i], wave_cnt);
            }
        }
    } while (!sim_finish && cycle < MAX_CYCLE);
    for (size_t i = 0; i < RENAME_WIDTH; i++)
    {
        top.o_allocate_ready[i] = 0;
    }

    free_cnt = 0;
    do
    {
        run();
        cycle++;
        for (size_t i = 0; i < COMMIT_WIDTH; i++)
        {
            top.i_free_valid[i] = 1;
        }
        sim_finish = true;
        for (size_t i = 0; i < COMMIT_WIDTH; i++)
        {
            if (top.i_free_ready && top.i_free_valid[i])
            {
                LOG("Free %ld at %ld", free_cnt, wave_cnt);
                top.i_free_entry[i] = free_cnt++;
                sim_finish = false;
            }
        }
    } while (!sim_finish && cycle < MAX_CYCLE);
    for (size_t i = 0; i < COMMIT_WIDTH; i++)
    {
        top.i_free_valid[i] = 0;
    }
}

int main()
{
#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top.trace(tfp, 0);
    tfp->open("Vfreelist.fst");
#endif
    srand(0);
    Verilated::randSeed(0);
    Verilated::randReset(0);

    test_reset();
    test_free();
    // Final model cleanup
    top.final();

#if TRACE
    tfp->close();
    delete tfp;
#endif
    return 0;
}